Magma has an experimental feature introducing a register file primitive.
To use this, first install the magma branch:
git clone https://github.com/phanrahan/magma
cd magma
git checkout regfile-primitive
pip install -e .
The RegisterFile
primitive is a Generator
that takes in arguments height, data_width
. This will create height
registers each storing a Bits[data_width]
.
To read from a register, you can use the __getitem__
syntax, such as reg_file[addr]
where addr
is a magma value of type Bits[clog2(height)]
. This will add a read port to the generated register file.
Simililarly, to write to a register, you can use the __setitem__
syntax, such as reg_file[addr] = data
where addr
is a magma value of type Bits[clog2(height)]
and data
is a magma value of type Bits[data_width]
. This will add a write port to the generated register file. NOTE that this uses =
(assignment) instead of @=
which is normally used for wiring.
The RegisterFile
uses last connect (write) semantics. If two statements write to the register file, and their dynamic address values match, the value written by the last executed statement will take priority.
The RegisterFile
forwards writes within the same cycle (combinational writes), so if a read statement reads from the same dynamic address as a write statement, the read value will equal the write value.
Planned features (feedback welcome):
wire
Here's a basic example and test
In [1]:
import magma as m
from magma.primitives.register_file import RegisterFile
height = 4
data_width = 4
addr_width = m.bitutils.clog2(height)
class Main(m.Circuit):
io = m.IO(
write_addr=m.In(m.Bits[addr_width]),
write_data=m.In(m.Bits[data_width]),
read_addr=m.In(m.Bits[addr_width]),
read_data=m.Out(m.Bits[data_width])
) + m.ClockIO(has_async_reset=True)
reg_file = RegisterFile(height, data_width)
reg_file[io.write_addr] = io.write_data
io.read_data @= reg_file[io.read_addr]
m.compile("build/test_register_file_primitive_basic", Main, inline=True)
In [ ]:
import fault
import tempfile
tester = fault.Tester(Main, Main.CLK)
tester.circuit.CLK = 0
for i in range(4):
tester.circuit.write_addr = i
tester.circuit.write_data = i
tester.step(2)
for i in range(4):
tester.circuit.read_addr = i
tester.eval()
tester.circuit.read_data.expect(i)
# Test combinational write
tester.circuit.read_addr = 1
tester.eval()
tester.circuit.read_data.expect(1)
tester.circuit.write_addr = 1
tester.circuit.write_data = 2
tester.eval()
tester.circuit.read_data.expect(2)
with tempfile.TemporaryDirectory() as dir_:
tester.compile_and_run("verilator", directory=dir_, flags=['-Wno-unused'])
Here's an example and test that demonstrates the "last connect/write" semantics
In [ ]:
import magma as m
from magma.primitives.register_file import RegisterFile
height = 4
data_width = 4
addr_width = m.bitutils.clog2(height)
class Main2(m.Circuit):
io = m.IO(
write_addr0=m.In(m.Bits[addr_width]),
write_data0=m.In(m.Bits[data_width]),
write_addr1=m.In(m.Bits[addr_width]),
write_data1=m.In(m.Bits[data_width]),
read_addr0=m.In(m.Bits[addr_width]),
read_data0=m.Out(m.Bits[data_width]),
read_addr1=m.In(m.Bits[addr_width]),
read_data1=m.Out(m.Bits[data_width])
) + m.ClockIO(has_async_reset=True)
reg_file = RegisterFile(height, data_width)
reg_file[io.write_addr0] = io.write_data0
io.read_data0 @= reg_file[io.read_addr0]
reg_file[io.write_addr1] = io.write_data1
io.read_data1 @= reg_file[io.read_addr1]
m.compile("build/test_register_file_primitive_two", Main2, inline=True)
import fault
import tempfile
tester = fault.Tester(Main2, Main2.CLK)
tester.circuit.CLK = 0
for i in range(4):
tester.circuit.write_addr0 = i
tester.circuit.write_data0 = 3 - i
tester.circuit.write_addr1 = 3 - i
tester.circuit.write_data1 = i
tester.step(2)
for i in range(4):
tester.circuit.read_addr0 = i
tester.circuit.read_addr1 = 3 - i
tester.eval()
tester.circuit.read_data0.expect(3 - i)
tester.circuit.read_data1.expect(i)
# Test priority
tester.circuit.write_addr0 = 3
tester.circuit.write_data0 = 3
tester.circuit.write_addr1 = 3
tester.circuit.write_data1 = 4
tester.step(2)
tester.circuit.read_addr0 = 3
tester.eval()
tester.circuit.read_data0.expect(4)
with tempfile.TemporaryDirectory() as dir_:
tester.compile_and_run("verilator", directory=dir_, flags=['-Wno-unused'])
In [ ]: